﻿using System;
using System.Linq;
using System.Xml;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;

namespace LinkPad.Shapes
{
    internal class ManagedLine : BaseManagedLine
    {
        #region Fields

        public static readonly Color DefaultStrokeColor = Colors.Green;
        public const double DefaultStrokeThickness = 4.0;

        private bool _boundingBoxInvalid;
        private Rect _boundingBox;

        #endregion

        #region Constructors

        public ManagedLine(ShapeManager shapeManager)
            : base(shapeManager)
        {
            InnerLine = new Line();
            StrokeColor = DefaultStrokeColor;
            StrokeThickness = DefaultStrokeThickness;
        }

        #endregion

        #region Properties

        #region IManagedShape

        public override Rect BoundingBox
        {
            get
            {
                if (_boundingBoxInvalid)
                {
                    var minX = Math.Min(X1, X2);
                    var maxX = Math.Max(X1, X2);
                    var minY = Math.Min(Y1, Y2);
                    var maxY = Math.Max(Y1, Y2);
                    _boundingBox.X = minX;
                    _boundingBox.Y = minY;
                    _boundingBox.Width = maxX - minX;
                    _boundingBox.Height = maxY - minY;
                    _boundingBoxInvalid = false;
                }
                return _boundingBox;
            }
        }

        #endregion

        public Line InnerLine { get; private set; }

        public double X1
        {
            get { return InnerLine.X1; }
            set
            {
                ShapeManager.ShapeChanged(this);
                RealX1 = ShapeManager.GetAbsoluteLocationX(value);
                if (Math.Abs(InnerLine.X1 - value) < double.Epsilon) return;
                InnerLine.X1 = value;
                _boundingBoxInvalid = true;
            }
        }

        public double Y1
        {
            get { return InnerLine.Y1; }
            set
            {
                ShapeManager.ShapeChanged(this);
                RealY1 = ShapeManager.GetAbsoluteLocationY(value);
                if (Math.Abs(InnerLine.Y1 - value) < double.Epsilon) return;
                InnerLine.Y1 = value;
                _boundingBoxInvalid = true;
            }
        }

        public double X2
        {
            get { return InnerLine.X2; }
            set
            {
                ShapeManager.ShapeChanged(this);
                RealX2 = ShapeManager.GetAbsoluteLocationX(value);
                if (Math.Abs(InnerLine.X2 - value) < double.Epsilon) return;
                InnerLine.X2 = value;
                _boundingBoxInvalid = true;
            }
        }

        public double Y2
        {
            get { return InnerLine.Y2; }
            set
            {
                ShapeManager.ShapeChanged(this);
                RealY2 = ShapeManager.GetAbsoluteLocationY(value);
                if (Math.Abs(InnerLine.Y2 - value) < double.Epsilon) return;
                InnerLine.Y2 = value;
                _boundingBoxInvalid = true;
            }
        }
        public double StrokeThickness
        {
            get { return InnerLine.StrokeThickness; }
            set { InnerLine.StrokeThickness = value; }
        }

        public Brush Stroke
        {
            get { return InnerLine.Stroke; }
            set { InnerLine.Stroke = value; }
        }

        public Color StrokeColor
        {
            get
            {
                var solidBrush = Stroke as SolidColorBrush;
                if (solidBrush != null)
                {
                    return solidBrush.Color;
                }
                return DefaultStrokeColor;
            }
            set
            {
                Stroke = new SolidColorBrush(value);
            }
        }

        #endregion

        #region Methods

        #region ManagedShape members

        public override void AddToCanvas()
        {
            ShapeManager.Canvas.Children.Add(InnerLine);
        }

        public override void RemoveFromCanvas()
        {
            ShapeManager.Canvas.Children.Remove(InnerLine);
        }

        public override int[] IndicesInCanvas()
        {
            var index = ShapeManager.Canvas.Children.IndexOf(InnerLine);
            return new []{index};
        }

        public override void InsertToCanvas(int index)
        {
            ShapeManager.Canvas.Children.Insert(index, InnerLine);
        }

        public override double DistanceToPoint(Point point)
        {
            var dx = X2 - X1;
            var dy = Y2 - Y1;
            var r = Math.Sqrt(dx*dx + dy*dy);
            var cosa = dx/r;
            var sina = dy/r;

            var shiftedX = point.X - X1;
            var shiftedY = point.Y - Y1;

            var rotatedX = shiftedX*cosa + shiftedY*sina;
            var rotatedY = -shiftedX*sina + shiftedY*cosa;

            if (rotatedX < 0)
            {
                return Math.Sqrt(rotatedX*rotatedX + rotatedY*rotatedY);
            }
            if (rotatedX > r)
            {
                var xvr = rotatedX - r;
                return Math.Sqrt(xvr*xvr + rotatedY*rotatedY);
            }
            return Math.Abs(rotatedY);
        }

        public override void Transform()
        {
            var scaleX = ShapeManager.ScaleX;
            var scaleY = ShapeManager.ScaleY;
            var offsetX = ShapeManager.OffsetX;
            var offsetY = ShapeManager.OffsetY;

            InnerLine.X1 = RealX1 * scaleX + offsetX;
            InnerLine.Y1 = RealY1 * scaleY + offsetY;
            InnerLine.X2 = RealX2 * scaleX + offsetX;
            InnerLine.Y2 = RealY2 * scaleY + offsetY;

            _boundingBoxInvalid = true;
        }

        public override void WriteToXml(XmlWriter xmlWriter)
        {
            xmlWriter.WriteStartElement("Line");
            xmlWriter.WriteAttributeString("X1", RealX1.ToString());
            xmlWriter.WriteAttributeString("Y1", RealY1.ToString());
            xmlWriter.WriteAttributeString("X2", RealX2.ToString());
            xmlWriter.WriteAttributeString("Y2", RealY2.ToString());
            xmlWriter.WriteAttributeString("Color", StrokeColor.ToString());
            xmlWriter.WriteEndElement();
        }

        public override bool ReadFromXml(XmlReader xmlReader)
        {
            var paramRead = new[] { false, false, false, false };
            var color = DefaultStrokeColor;
            while (xmlReader.MoveToNextAttribute())
            {
                switch (xmlReader.Name)
                {
                    case "X1":
                        RealX1 = Convert.ToDouble(xmlReader.Value);
                        paramRead[0] = true;
                        break;
                    case "Y1":
                        RealY1 = Convert.ToDouble(xmlReader.Value);
                        paramRead[1] = true;
                        break;
                    case "X2":
                        RealX2 = Convert.ToDouble(xmlReader.Value);
                        paramRead[2] = true;
                        break;
                    case "Y2":
                        RealY2 = Convert.ToDouble(xmlReader.Value);
                        paramRead[3] = true;
                        break;
                    case "Color":
                        var sclr = xmlReader.Value;
                        var a = Convert.ToByte(sclr.Substring(1, 2), 16);
                        var r = Convert.ToByte(sclr.Substring(3, 2), 16);
                        var g = Convert.ToByte(sclr.Substring(5, 2), 16);
                        var b = Convert.ToByte(sclr.Substring(7, 2), 16);
                        color = Color.FromArgb(a, r, g, b);
                        break;
                }
            }

            Stroke = new SolidColorBrush(color);

            xmlReader.MoveToElement();

            return paramRead.All(b => b);
        }

        #endregion

        #endregion
    }
}
